#include "stdafx.h"
#include <MyControls/Timeline.h>
#include <MyControls/TimeRuler.h>
#include <MyControls/Resource.h>

namespace My {

const int NavigatorHeight	= 8;
const int ThumbLineHeight	= 13;
const int ZoomSliderWidth	= 60;
const int ZoomButtonWidth	= 25;

Timeline::Timeline() :
	m_pSequence(0),
	m_Navigator(* this),
	m_ThumbLine(* this),
	m_TimeRuler(* this),
	m_TimeSlider(* this),
	m_ScaleIn(* this),
	m_ScaleOut(* this)
{
	m_TimeScale = 1;
	m_TimeOffset = 0;
	m_XAutoScroll = 0;

	m_CurrentFrame = -1;
	m_WorkArea = Span(0, 0);

	Add(&m_Navigator);
	Attach(&m_Navigator);

	Add(&m_ThumbLine);
	Attach(&m_ThumbLine);

	Add(&m_TimeRuler);
	Attach(&m_TimeRuler);

	m_AutoScroll.SetDelay(100);
	m_AutoScroll.Attach(this);

	static_cast<EventDispatcher &>(m_XScrollBar).Attach(this);
	m_XScrollBar.m_Style = SBS_HORZ|WS_CHILD|WS_VISIBLE;
	Add(&m_XScrollBar);

	Attach(&m_TimeSlider);
	Attach(&m_ScaleIn);
	Attach(&m_ScaleOut);

	m_Gap = 15;
	m_TimeScrollLine = 50;
}

Timeline::~Timeline()
{
	SetSequence(0);

	Detach(&m_Navigator);
	Detach(&m_ThumbLine);
	Detach(&m_TimeRuler);
}

void Timeline::SetSequence(My::Sequence * pSequence)
{
	if (m_pSequence != pSequence) {
		if (m_pSequence != 0) {
			m_pSequence->Detach(this);
			m_pSequence = 0;
			EventDispatcher::DispatchEvent(Detached(* this));
		}
		m_pSequence = pSequence;
		if (pSequence != 0) {
			pSequence->Attach(this);
			EventDispatcher::DispatchEvent(Attached(* this));
		}
	}
}

void Timeline::ProcessEvent(My::Event & e)
{
	if (e.Is<Sequence::Event>()) { 
		if (e.Is<Sequence::OpenEvent>())
			OnOpen();
		else if (e.Is<Sequence::CloseEvent>())
			OnClose();
		else if (e.Is<Sequence::CurrentFrameChange>())
			OnCurrentFrameChanged();
		else if (e.Is<Sequence::WorkAreaChange>())
			OnWorkAreaChanged();
		else if (e.Is<Sequence::StartChange>())
			OnStartChanged();
		else if (e.Is<Sequence::DurationChange>())
			OnDurationChanged();
		EventDispatcher::DispatchEvent(e); // forward Sequence events
	} 
	else if (e.Is<TimerEvent>())
		OnAutoScroll(true);
	else if (e.Is<ScrollEvent>()) {
		ScrollEvent & event = e.As<ScrollEvent>();
		OnTimeScroll(event.m_Code, event.m_Pos);
	} 
	else {
		if (e.Is<CommandEvent>()) {
			CommandEvent & event = e.As<CommandEvent>();
			switch (event.m_nID) {
				case ID_TIMELINE_FIT_IN_WINDOW:
					SetTimeScale(GetMinTimeScale());
					event.m_Handled = true;
					break;
				case ID_TIMELINE_ZOOM_IN:
					SetTimeScale(GetTimeScale() * 2);
					event.m_Handled = true;
					break;
				case ID_TIMELINE_ZOOM_OUT:
					SetTimeScale(GetTimeScale() / 2);
					event.m_Handled = true;
					break;
			}
		}
		else if (e.Is<UpdateCommandUIEvent>()) {
			UpdateCommandUIEvent & event = e.As<UpdateCommandUIEvent>();
			switch (event.m_nID) {
				case ID_TIMELINE_FIT_IN_WINDOW:
					event.m_pCmdUI->Enable(!IsEmpty() &&
						GetTimeScale() > GetMinTimeScale());
					event.m_Handled = true;
					break;
				case ID_TIMELINE_ZOOM_IN:
					event.m_pCmdUI->Enable(!IsEmpty() &&
						GetTimeScale() < GetMaxTimeScale());
					event.m_Handled = true;
					break;
				case ID_TIMELINE_ZOOM_OUT:
					event.m_pCmdUI->Enable(!IsEmpty() &&
						GetTimeScale() > GetMinTimeScale());
					event.m_Handled = true;
					break;
			}
		}
		Container::ProcessEvent(e);
	}
}

CSize Timeline::GetPreferredSize() const
{
	return CSize(0, 
		NavigatorHeight + 
		ThumbLineHeight + 
		m_TimeRuler.GetPreferredSize().cy + 
		::GetSystemMetrics(SM_CYHSCROLL));
}

void Timeline::LayoutControls()
{
	int w = ClientWidth();
	int h = ClientHeight();
	m_Navigator.SetBounds(0, 0, 
		w, NavigatorHeight);
	m_ThumbLine.SetBounds(0, NavigatorHeight, 
		w, NavigatorHeight + ThumbLineHeight);
	m_TimeRuler.SetBounds(0, 
		NavigatorHeight	+ ThumbLineHeight, 
		w, 
		NavigatorHeight	+ ThumbLineHeight + m_TimeRuler.GetPreferredSize().cy);
	m_XScrollBar.SetBounds(ZoomSliderWidth + 2 * ZoomButtonWidth, h - ::GetSystemMetrics(SM_CYHSCROLL), w, h);
	m_ScaleOut.SetBounds(0, h - ::GetSystemMetrics(SM_CYHSCROLL), ZoomButtonWidth, h);
	m_TimeSlider.SetBounds(ZoomButtonWidth, h - ::GetSystemMetrics(SM_CYHSCROLL), ZoomButtonWidth + ZoomSliderWidth, h);
	m_ScaleIn.SetBounds(ZoomButtonWidth + ZoomSliderWidth, h - ::GetSystemMetrics(SM_CYHSCROLL), 2 * ZoomButtonWidth + ZoomSliderWidth, h);
}

void Timeline::HandleResize(int sx, int sy)
{
	Widget::HandleResize(sx, sy);
	UpdateTimeScale();
	UpdateTimeOffset();
	UpdateXScrollBar();
}

void Timeline::SetCurrentFrame(int t)
{
	if (IsEmpty())
		return;
	t = GetSequence()->GetTimeSpan().Bound(t);
	if (m_CurrentFrame != t) {
		m_CurrentFrame = t;
		EventDispatcher::DispatchEvent(CurrentFrameChange(* this));
		GetSequence()->SetPreviewFrame(t);
	}
}

void Timeline::SetWorkArea(Span s)
{
	if (IsEmpty())
		return;
	s.Offset(- GetSequence()->GetStart());
	if (s != m_WorkArea) {
		m_WorkArea = s;
		EventDispatcher::DispatchEvent(WorkAreaChange(* this));
	}
}

void Timeline::UpdateWorkArea()
{
	if (!IsEmpty())
		GetSequence()->SetWorkArea(GetWorkArea());
}

void Timeline::SetTimeScale(double scale)
{
	if (IsEmpty())
		return;
	double min = GetMinTimeScale(), max = GetMaxTimeScale();
	if (scale > max)
		scale = max;
	if (scale < min)
		scale = min;
	if (scale != m_TimeScale)
	{
		int x = TimeToX(GetCurrentFrame());
		m_TimeScale = scale;
		UpdateTimeScale();
		UpdateTimeOffset();
		UpdateXScrollBar();
		EventDispatcher::DispatchEvent(TimeScaleChange(* this));
		SetTimeOffset(m_TimeOffset + GetCurrentFrame() - XToTime(x));
	}
}

void Timeline::SetTimeOffset(double offset)
{
	if (IsEmpty())
		return;
	Span s = GetSequence()->GetTimeSpan();
	if (offset > s.last - GetTimePage())
		offset = s.last - GetTimePage();
	if (offset < s.first)
		offset = s.first;
	if (offset != m_TimeOffset)
	{
		m_TimeOffset = offset;
		UpdateXScrollBar();
		EventDispatcher::DispatchEvent(TimeOffsetChange(* this));
	}
}

int Timeline::GetXPage() const
{
	return m_TimeRuler.Width();
}

void Timeline::SetTimeAutoScroll(int d)
{
	m_XAutoScroll = d; 
	m_AutoScroll.Enable(d != 0);
}

void Timeline::OnAutoScroll(bool mouseMove)
{
	if (m_XAutoScroll != 0)
	{
		SetXOffset(GetXOffset() + m_XAutoScroll);
		if (mouseMove) {
		// Simulate mouse movement:
			CPoint p; ::GetCursorPos(&p);
			My::MouseMoveEvent e(0, p);
			Control * pControl = GetCaptureControl();
			if (pControl == 0)
				pControl = this;
			pControl->ProcessEvent(My::MouseMoveEvent(0, pControl->ScreenToClient(p)));
		}
	}
}

void Timeline::OnTimeScroll(UINT nCode, UINT nPos)
{
	int x = GetXOffset();

	switch (nCode) {
	case SB_LEFT:
		x = 0;
		break;
	case SB_ENDSCROLL:
		break;
	case SB_LINELEFT:
		x -= m_TimeScrollLine;
		break;
	case SB_LINERIGHT:
		x += m_TimeScrollLine;
		break;
	case SB_PAGELEFT:
		x -= GetXPage();
		break;
	case SB_PAGERIGHT:
		x += GetXPage();
		break;
	case SB_RIGHT:
		x = GetXRange() - GetXPage();
		break;
	case SB_THUMBPOSITION:
	case SB_THUMBTRACK:
		x = nPos;
		break;
	default:
		return;
	}
	SetXOffset(x);
	Update();
}

void Timeline::OnOpen()
{
	UpdateTimeScale();
	UpdateTimeOffset();
	UpdateXScrollBar();

	SetTimeScale(GetMinTimeScale());
	SetWorkArea(GetSequence()->GetWorkArea());
	SetTimeOffset(GetSequence()->GetStart());
	SetCurrentFrame(GetSequence()->GetCurrentFrame());

	Invalidate();
}

void Timeline::OnClose()
{
	m_TimeScale = 1;
	m_TimeOffset = 0;
	m_CurrentFrame = -1;
	m_WorkArea = Span(0, 0);

	UpdateTimeScale();
	UpdateTimeOffset();
	UpdateXScrollBar();

	Invalidate();
}

void Timeline::OnStartChanged()
{
	UpdateTimeScale();
	UpdateTimeOffset();
	UpdateXScrollBar();

	Invalidate();
}

void Timeline::OnDurationChanged()
{
	UpdateTimeScale();
	UpdateTimeOffset();
	UpdateXScrollBar();

	Invalidate();
}

void Timeline::UpdateXScrollBar()
{
	SCROLLINFO info;
	info.cbSize = sizeof(SCROLLINFO);     
	info.fMask = SIF_ALL|SIF_DISABLENOSCROLL;     
	info.nMin = 0;     
	info.nMax = GetXRange(); 
	info.nPage = GetXPage();
	info.nPos = GetXOffset();
	info.nTrackPos = GetXOffset();
	if (info.nMax == info.nPage)
		info.nPage++;
	m_XScrollBar.SetInfo(info);
}

} // My